Date & Time: 2026-04-10 08:26:33 Location: [Insert Location] Speaker: Kevin Sullivan
Software Engineering Artificial Intelligence Formal Verification Programming Languages LLMs Deductive Reasoning Inductive Reasoning Lean Functional Programming Theorem Proving Curry-Howard Correspondence Data Types Program Verification Type Theory Software Specification
The Problem with Modern AI-Generated Code: AI coding assistants (LLMs) are revolutionary for productivity but produce code with extremely low reliability (e.g., 1 error in 10 invocations), making them unsuitable for mission-critical systems like avionics, which require failure rates as low as 1 in a billion hours (10^-9). This massive reliability gap poses significant risks for large-scale, high-stakes software.
Software Reliability in Critical Systems: This topic defines the stringent reliability standards required for mission-critical software, such as avionics. The core requirement is near-absolute certainty of correctness, expressed numerically as a failure rate of 10^-9 per hour, which is intended to ensure the software never fails during the entire fleet's lifespan. It highlights that testing alone is insufficient to prove the absence of bugs.
Deductive vs. Inductive Reasoning in Software: This point explains the fundamental difference between traditional programming and modern AI. Traditional software operates on deductive logic (like Hoare logic), where given correct inputs and logic, the output is guaranteed to be correct. AI and LLMs operate on inductive reasoning, making predictions based on data, which is inherently probabilistic, unsound, and leads to "hallucinations" or errors.
The Future of Programming: The Convergence of Inductive and Deductive Reasoning: The future isn't about one type of reasoning replacing the other, but their marriage. LLMs (inductive) can be used to generate formal proofs, which are then verified by deductive systems like the Lean proof assistant. This synergy dramatically lowers the cost and effort of creating provably correct software, making it a viable and superior alternative to traditional coding methods for building reliable systems.
Curry-Howard Correspondence: This principle establishes that mathematical logic (specifically, constructive logic) and computer programming (specifically, functional programming) are isomorphic. It means that propositions can be represented as types, and proofs of those propositions can be represented as values (or objects) of those types. This allows for theorem proving to be treated as a form of programming.
Function Types and Logical Implication: In programming, a function type alpha -> beta represents a function that takes a value of type alpha and returns a value of type beta. In logic, P -> Q (P implies Q) is proven by a function that takes any proof of proposition P and returns a proof of proposition Q. The arrow -> has the same meaning in both contexts: a transformation from one thing to another.
Product Types and Logical Conjunction (AND): In programming, a product type alpha * beta is the type of ordered pairs where the first element is of type alpha and the second is of type beta. To construct a value of this type, you need a value of type alpha and a value of type beta. In logic, a proof of P AND Q is a pair containing a proof of P and a proof of Q.
Sum Types and Logical Disjunction (OR): In programming, a sum type alpha + beta (also known as a tagged union) is a type that holds a value of either type alpha or type beta. Code that uses a sum type value must first perform a case analysis to determine which type of value is actually present. In logic, a proof of P OR Q is an object containing either a proof of P or a proof of Q. To use such a proof, one must perform a case analysis on which proof is present.
Introduction to Lean for CS1: This knowledge point introduces a course designed to teach functional programming using the Lean language, structured as a CS1 course. It highlights that the course materials are available on a specific GitHub website (kevinsullivan.github.io/lean4cs1), exist in multiple versions of varying detail, and require Docker for local setup.
Core Concepts of Lean Programming: This knowledge point explains the fundamental elements of programming in Lean. It covers atomic data types like natural numbers (nat), strings, and booleans, along with basic operations on them. It introduces key commands: #eval to compute the value of an expression (like a REPL) and #check to determine the type of an expression.
Propositions as Types (Curry-Howard Correspondence): This explains the central idea that logical propositions are treated as types in Lean. A proof of a proposition is a value (or inhabitant) of that type. A true proposition is a type that has at least one value, while a false proposition is an empty type with no values. This allows logical reasoning to be performed through the mechanics of functional programming.
Writing Predicates and Theorems in Lean: This knowledge point details how to represent logical predicates and prove theorems. Predicates are implemented as functions that return a proposition (or a boolean). Theorems are also functions that, given some inputs, return a proof for a specific proposition. Proofs can be constructed using "tactics," which are programs that automate the application of logical inference rules.
Program Verification and High-Assurance Systems: This covers the application of Lean for building highly reliable software by formally verifying its correctness. The process involves creating a formal specification of a program's behavior as a type and then proving that the implementation code satisfies that specification. This method is contrasted with traditional testing, which cannot guarantee the absence of bugs, especially in critical components like compilers or hardware.
The Future of Software Development: Writing Specifications: This logistical and conceptual point argues that the most critical skill for future software developers will be writing precise logical specifications, rather than just implementation code. With the rise of AI tools (LLMs) that can assist in generating code and proofs, the developer's primary role will shift to defining exactly what the software should do (the "what," not the "how").
Course Logistics and Future Sessions: This covers logistical details for the class. It mentions the course schedule, including that there is one more lecture next week on vision language models, followed by student presentations. The presentation schedule is outlined as five presentations on Thursday, then six, and then five in the subsequent weeks.
Key Points
AI and LLMs offer a huge relief from tedious typing, which can be a physical pain and a blocker to productivity after many years.
For large systems (100,000 to 500,000 lines of code), it's nearly impossible to know if AI-generated code is free of critical problems.
Issues may only be discovered after the code is in production, potentially leading to catastrophic consequences like financial loss or security breaches.
Relying on AI for personal projects is fine, but it is not believed to be sufficient for creating software with high reliability for mission-critical environments.
Explanation The speaker, Kevin Sullivan, questions how one can comprehend and trust a massive codebase (100,000 lines of Python, C++, or Java) generated by an AI and be sure it isn't "full of bugs." While testing can reveal the presence of bugs, it cannot prove their absence, a concept attributed to Dijkstra. Exhaustive testing is computationally impossible for most non-trivial inputs. For example, testing all possible inputs for two 64-bit floating-point numbers would require checking 2 to the 128 combinations, a number larger than the estimated number of fundamental particles in the universe. Therefore, one cannot simply test their way to achieving high reliability with AI-generated code.
Key Points
Flight avionics software is in a regulated industry and requires FAA approval; a standard Python program cannot be used for an autopilot.
To pass FAA muster, software must demonstrably fail no more than 10 to the minus nine times per hour (1 failure per billion hours).
This standard is intended to cover the entire lifespan of the entire fleet, meaning the software should effectively never crash the plane.
The Boeing incidents where planes crashed due to software are cited as examples of engineering failure, where a single point of failure (one sensor instead of two) led to disaster.
Other critical systems, like insulin pumps, have slightly less stringent but still high reliability requirements, such as 10 to the minus six or seven failures per hour.
Explanation The discussion contrasts the reliability of current LLMs with the requirements for critical systems. While an LLM might be considered good if it fails only one in ten times (a 10^-1 failure rate), mission-critical software requires "nine nines" of reliability (10^-9). This is a difference of 10 to the eighth, meaning the LLM is a hundred million times less reliable than what is required for something like flight control software. This discrepancy highlights the fundamental unsuitability of current generative AI for such applications. The source of this unreliability is rooted in the type of reasoning the systems use.
Key Points
Traditional software is deductive. A command like if x beta represents a function that consumes a value of type alpha and returns a value of type beta.
In logic, P -> Q means "P implies Q".
A proof of the implication P implies Q is a function that takes a proof of P and returns a proof of Q.
If P is true, it means there is a proof of it. If you can always turn a proof of P into a proof of Q, then "P implies Q" is true.
If you cannot define such a function (because you lack enough information to complete the code), you do not have a proof of the implication.
Explanation
The function type notation alpha -> beta is familiar in functional programming. It describes a total function that can turn any object of type alpha into an object of type beta. The Curry-Howard correspondence shows that this exact same "arrow" notation has an identical meaning in logic. The logical proposition P implies Q (written P -> Q) means that if P is true, then Q must also be true. In this constructive logic system, a proposition is true if there exists a proof for it. Therefore, a proof of P implies Q is literally a function that, when given any proof of P as its input, is guaranteed to produce a proof of Q as its output. If you can successfully write this function, you have formally proven the implication.
Key Points
A product type alpha * beta is the type of ordered pairs where the first element is of type alpha and the second is of type beta.
A value of this type is an ordered pair, e.g., (a, b) where a is of type alpha and b is of type beta.
To construct a value of type alpha * beta, you need a value of type alpha AND a value of type beta.
In logic, a proof of the conjunction P AND Q is a pair of proofs: one proof for P and one proof for Q.
Explanation
In advanced functional programming languages, you can define product types. The type nat * string is the type of all ordered pairs where the first element is a natural number and the second is a string. To create a value of this type, you must provide both components. This "AND" relationship maps directly to logical conjunction. To prove the proposition P AND Q (e.g., "It's raining outside AND I'm hungry"), you need to provide a proof for P ("it's raining") and a proof for Q ("I'm hungry"). A proof of the conjunction P AND Q is therefore an ordered pair of proofs, (proof_of_p, proof_of_q).
Example 1: Proving (P AND Q) -> P
The goal is to prove that if "P and Q" is true, then "P" is true. In programming terms, we need to write a function that takes a proof of
P AND Qand returns a proof ofP. A proof ofP AND Qis a pair(proof_p, proof_q). The function simply needs to take this pair as input and return its first element.
Let's assume P is "It's raining outside" and Q is "I'm hungry". The proposition to prove is: "If it's raining outside and I'm hungry, then it's raining outside."
A proof of an implication is a function. This function will take an argument which is a proof of "It's raining and I'm hungry".
A proof of a conjunction is a pair. So the argument to our function is a pair, let's call it hr, which contains (proof_that_its_raining, proof_that_im_hungry).
To return a proof that "It's raining," the function just needs to access the first element of the pair. If the pair is hr, the code would be something like hr.1 (or hr.first). This successfully produces a proof of P from a proof of P AND Q.
Key Points
A sum type alpha + beta (also called a tagged union) is a data structure that can hold either a value of type alpha or a value of type beta, but not both.
The object contains a "tag" to indicate which type of value is currently stored.
When you receive a value of a sum type, you must perform a case analysis (e.g., if tag == 'alpha' ... else if tag == 'beta' ...) to handle it correctly.
In logic, a proof of the disjunction P OR Q is an object that contains either a proof of P or a proof of Q.
To use a proof of P OR Q to prove something else, you must do a case analysis: show that your conclusion follows in the case you have a proof of P, and also show that it follows in the case you have a proof of Q.
Explanation
In programming languages like C, a union type allows a variable to hold values of different types at different times. A more robust version is a "tagged union," which includes a tag indicating what type of data is currently inside. This is known as a sum type in functional programming. It represents an "OR" condition: the value is either a string or an int. Any code that operates on this value must first check the tag and branch its logic accordingly—a case analysis. This maps directly to logical disjunction (OR). A proof of P OR Q is a container that holds either a proof of P or a proof of Q. To construct one, you just need a proof of one of them. To use one, for example to prove (P OR Q) -> R, you must handle both cases. You have to prove that P -> R AND that Q -> R. If you can show the conclusion R holds regardless of whether you start with P or Q, then you have proven that (P OR Q) -> R.
Example 1: Reasoning with Disjunction
If we want to prove that "if the sprinkler is running OR it's raining, then the street is wet." We assume we are given a proof of "the sprinkler is running OR it's raining."
The proof we are given is a sum type object. It either contains a proof that "the sprinkler is running" or a proof that "it's raining."
We must perform a case analysis to use this proof.
Case 1: The proof object contains a proof that "the sprinkler is running." If we also have a proof of the implication "if the sprinkler is running, then the street is wet," we can combine these to conclude "the street is wet."
Case 2: The proof object contains a proof that "it's raining." If we also have a proof of the implication "if it's raining, then the street is wet," we can combine these to conclude "the street is wet."
Since the conclusion ("the street is wet") can be reached in both possible cases, we have successfully used the proof of the disjunction to prove our desired outcome.
Key Points
The course material is available online at kevinsullivan.github.io/lean4cs1.
The material is intended to be a CS1 (introductory computer science) programming course using Lean.
Three different versions of the course are available on the website: a compact version, a 9-10 week version for beginners, and a more detailed, developed version.
The website includes a link to the GitHub repository for the course materials.
To run the course materials locally, you need to fork the repository, clone it, open it in VS Code, and have Docker installed on your machine.
Explanation
The speaker, Kevin Sullivan, introduces a course he has developed called "Lean for CS1." The content is hosted at kevinsullivan.github.io/lean4cs1. This course is designed as an introductory programming course, similar to a standard CS1 curriculum, but using the Lean language. The website offers three versions of the material, catering to different needs: a very compact version, a longer 9 to 10-week version for those with no prior programming experience (which can be combined with AI-based programming), and a much more detailed version. Users can access the project's Git repository from the website, fork it, and clone it to their local machine. To get the environment working correctly in VS Code, Docker must be installed.
Key Points
Lean supports atomic types such as nat for natural numbers, string, and bool.
It provides standard arithmetic and string operations.
The #eval command is used to evaluate an expression and see its result, similar to a REPL in Python.
The #check command is used to check the type of a given expression.
Explanation
Lean is presented as a nice and very capable functional programming language. It includes basic (atomic) data types like nat for natural numbers, as well as strings and booleans, with the usual operations defined for them. The speaker demonstrates two fundamental commands. The first is #eval, which functions like a Read-Eval-Print Loop (REPL). For example, #eval 2 + 3 will compute the sum and output the result. The second command is #check, which takes an expression and returns its type. This is crucial for understanding how Lean's type system works.
Example 1: Evaluating a simple expression
#eval 2 + 3
This command tells Lean to evaluate the expression 2 + 3.
Lean recognizes + as natural number addition and applies it to the arguments 2 and 3.
The result, 5, is computed and displayed.
Key Points
The Curry-Howard correspondence states that logical reasoning is equivalent to functional programming.
In Lean, a logical proposition is a type.
A proof of a proposition is a value (or "inhabitant") of that type.
A true proposition is a type that has at least one proof/value.
A false proposition is an empty type, meaning it has no values/proofs.
Negation is represented by functions that map to an empty type.
Explanation
A core concept of Lean is the Curry-Howard correspondence, which equates logic and computation. Under this paradigm, a proposition like 2 + 3 = 5 is not an assertion to be evaluated as true or false, but rather a type. A "proof" of this proposition is simply a value that has this type. For example, if a proposition is true, like "Kevin has a Virginia driver's license," a proof would be the physical license itself; this means the type is "inhabited." Conversely, a false proposition, such as "Kevin has a West Virginia driver's license," is an empty type—a type for which no value can ever be constructed. Falsity is therefore represented by empty types. Negation of a proposition P is understood as a function from P to an empty type. This entire framework allows logical deduction to be performed as a form of type-safe functional programming.
Example 1: A True Proposition with a Proof
Proposition:
2 + 3 = 5. A piece of Lean code can compute a proof of this.
The code first reduces 2 + 3 to 5 by computation.
This results in the proposition 5 = 5.
An axiom of equality states that for any A, A = A is true and a proof can be generated for free. This proof becomes the "value" for the type 2 + 3 = 5.
Example 2: Custom Propositions
rainingandground_wetare defined as types within thePropuniverse. The propositionrainingis assigned to be equal totrue, which always has a proof, making it an always-true proposition.
These are defined as types that are specifically propositions.
By equating raining to true, it is established as a proposition that is inhabited (i.e., true).
Example 3: True vs. False Propositions as Types
The proposition "I have a Virginia driver's license" is a type. The proof is the physical license from the wallet. This type is inhabited. The proposition "I have a West Virginia driver's license" is also a type, but it is false. There is no proof of it, so it is an empty type.
This analogy illustrates that a true statement corresponds to a type that has a member (a proof).
A false statement corresponds to a type that has no members (an empty type).
Key Points
Predicates in predicate logic are represented as functions in Lean.
These functions typically take one or more arguments and return a Prop (a proposition) or a bool.
Theorems are also defined as functions. A theorem's type signature specifies that for given inputs, it returns a proof of a proposition.
"Tactics" are programs/macros that automate parts of proof construction by applying rules of inference.
Universal quantification (for all) and implication (if-then) are identical in Lean's constructive logic, just with different notation.
Explanation
In Lean, you can define predicates from logic as simple functions. For instance, an is_even predicate can be a function that takes a natural number n and returns the boolean result of n mod 2 == 0. Similarly, theorems are also just functions. For example, a theorem stating that double n = n + n for any natural number n would be a function that takes n as an argument and returns a proof of the proposition double n = n + n. The body of this function is often a "tactic script." Tactics are powerful helper programs that automate the step-by-step application of logical rules, making proof construction much more manageable. The speaker also notes that in Lean's logic, implication (P -> Q, or "if P then Q") and universal quantification (∀x, P(x)) are fundamentally the same concept, representing a function from a proof of the premise to a proof of the conclusion.
Example 1: Predicate as a function
is_evenis a predicate that takes a natural numbernand returns abool. It's defined asn mod 2 = 0.
is_even 6 is a function call that returns true.
is_even 3 is a function call that returns false.
Example 2: A Theorem as a Function
Theorem: For every natural number
n,double nis equal ton + n.
The theorem is a function named my_theorem (hypothetically).
Its type is: given any natural number n, it returns a proof of the proposition double n = n + n.
The body of the function is a tactic script that constructs this proof automatically.
Key Points
Program verification aims to prove that a program meets its specification.
In Lean, the specification is encoded as a type, and the program is a function that produces a value of that type.
This approach can be used to verify complex systems, such as compilers.
Verification can uncover bugs that testing might miss, such as bugs in hardware (e.g., the Intel floating-point divide bug), or bugs in the compiler itself.
To verify a compiler (e.g., from C to x86), one must prove that the semantics (behavior) of the compiled code is equivalent to the semantics of the source code.
Explanation Lean can be used for formal program verification to build high-assurance systems. The traditional approach involved writing a program in one language (e.g., an imperative one) and its specification in another (e.g., predicate logic), then proving they match. In Lean, the specification is the type of the program. For a complex program, this can be a very complicated type. This method can provide a level of confidence that is impossible to achieve through testing. The speaker discusses potential points of failure in a typical software pipeline: hardware bugs (like the historic Intel CPU floating-point divide bug), environmental issues (cosmic rays), and, crucially, compiler bugs. To create a "perfect compiler," one can implement the source language (e.g., C), the target language (e.g., x86), and the compiler itself within Lean. Then, one can write a proposition stating that for any given C program, the semantics of the compiled x86 code is equivalent to the semantics of the C source. Finally, one proves this proposition within Lean. If the proof type-checks, you have an exceptionally high degree of confidence that the compiler is correct.
Example 1: Proving a Compiler Correct
To prove a C-to-x86 compiler is correct:
You define the semantics of the C language and the x86 instruction set within Lean.
You implement the compiler as a function within Lean that transforms a C program data structure into an x86 program data structure.
You write a theorem (a proposition) that states: for any C program c, the semantics of compile(c) is equal to the semantics of c.
You then construct a proof for this theorem. If the proof is accepted by Lean's type checker, the compiler is formally verified.
Key Points
The most important skill for software developers going forward will be writing logical specifications.
Developers will need to shift from focusing on implementation details to defining precise propositions about what the code should do.
Large Language Models (LLMs) will likely assist with generating implementation code and proof details.
The developer's core responsibility will be to correctly and precisely formulate the specification.
Becoming fluent in the "language of specification" is critical for future success.
Explanation The speaker argues that the software development landscape is shifting. The ability to write implementation code is becoming less of a differentiator, especially with the rise of AI tools like LLMs that can generate code and even assist with proofs. The truly critical and difficult task will be to write the specifications—the formal, logical propositions that define what a program is supposed to do. A developer's value will come from their ability to think clearly about requirements and translate them into a precise specification. The AI can then help fill in the "how," but the developer must define the "what." Therefore, learning the language of formal specification, as taught in this Lean course, is presented as an essential skill for the next generation of software engineers.
Key Points
There is one more lecture remaining in the course, scheduled for next week.
The topic of the final lecture is "vision language models."
Following the final lecture, student project presentations will begin.
The presentation schedule is: 5 presentations on the first day (Thursday), 6 presentations in the following week, and 5 presentations on the last day.
Students are encouraged to be creative with their final video submissions, including special effects.
Explanation Henry Kautz outlines the remaining schedule for the course. There is one final lecture planned for the upcoming week, which will cover vision language models. After this lecture, the class will move into final project presentations. The presentations will be spread across several sessions: the first session, on the Thursday a week from the lecture, will feature five presentations. The following week will have one session with six presentations and a final session with five presentations. Students are also reminded to finish their video projects and are encouraged to have fun and include creative elements.
Review the concepts of functional programming, specifically function types, product types (pairs), and sum types (tagged unions).
Finish up videos for final projects.
AI Suggestions
The core of this lesson is understanding the convergence of inductive and deductive reasoning and the Curry-Howard correspondence. It's recommended to start by exploring a simple example in a proof assistant like Lean to grasp how logical propositions are treated as types and how AI can assist in proof generation.
Core content of 'The Future of Programming as a Marriage of Inductive and Deductive Reasoning': The future of reliable software lies in combining the predictive power of AI (inductive reasoning) to generate proofs of correctness, with the infallible verification power of proof assistants like Lean (deductive reasoning), making provably correct software cheap and accessible.
Core content of 'Curry-Howard Correspondence': Propositions are represented as types, and proofs are represented as values of those types. This means proving a logical statement is equivalent to writing a well-typed function.
Extracurricular Resources: